"""
Copyright © 2020 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/ESP32-BLE-MI_Temperature_Humidity_2_Reader
"""
import bluetooth as bt
from ble.const import BLEConst
from ble.tools import BLETools
from ble.miot_factory import MIOTFactory
import gc


class MIOTException(Exception):
	pass


class BLEMIOT(object):
	def __init__(self, ble: bt.BLE, scan_completed_cb=None):
		self.__ble = ble
		self.__factory = MIOTFactory()
		self.__scan_completed_cb = scan_completed_cb

		self.__ble.active(False)
		print("activating ble...")
		self.__ble.active(True)
		print(f'ble activated [{BLETools.decode_mac(self.__ble.config('mac')[1])}]')

		self.__ble.config(mtu=256)
		self.__ble.irq(self.__irq)

	def scan(self, senconds=5):
		self.__factory.clear()
		print("scaning for {} second(s)...".format(senconds))
		self.__ble.gap_scan(senconds * 1000, 50000, 50000)

	def __irq(self, event, data):
		if event == BLEConst.IRQ.SCAN_RESULT:
			if MIOTFactory.check(data[4]):
				self.__factory.append(data)

		elif event == BLEConst.IRQ.SCAN_DONE:
			print("scan completed, {} MIOT device(s) found".format(self.__factory.count()))
			
			if self.__scan_completed_cb is not None:
				self.__scan_completed_cb(self.__factory.devices())

		elif event == BLEConst.IRQ.PERIPHERAL_CONNECT:
			device = self.__factory.find(data[2])

			if device:
				print("[{}] connected".format(device.name))
				device.conn_handle = data[0]
				self.__ble.gattc_discover_services(device.conn_handle)

		elif event == BLEConst.IRQ.PERIPHERAL_DISCONNECT:
			device = self.__factory.find(data[2])

			if device:
				print("[{}] disconnected".format(device.name))
				device.conn_handle = None

		elif event == BLEConst.IRQ.GATTC_NOTIFY:
			device = self.__factory.find(conn_handle=data[0])

			if device:
				temp, humi = self.__convert_temp_and_humi(data[2])
				print("[{}] notify: temp={}C, humi={}%".format(device.name, temp, humi))

		elif event in (BLEConst.IRQ.GATTC_SERVICE_RESULT,
				 	   BLEConst.IRQ.GATTC_SERVICE_DONE,
					   BLEConst.IRQ.CONNECTION_UPDATE):
			pass

		else:
			print("event: {}, data: {}".format(event, data))

		gc.collect()

	def connect_to_device(self, device):
		self.__ble.gap_connect(device.addr_type, device.addr)
	
	def __convert_temp_and_humi(self, value):
		assert isinstance(value, (bytes, memoryview)) and len(value) == 5, MIOTException("temp & humi value error")

		temp = int.from_bytes(value[:2], "little", False) / 100
		humi = value[2]

		return temp, humi


def main():
	def scan_completed_cb(devices):
		for device in devices:
			if device.adv_type in (BLEConst.ADVType.DIRECT_IND, BLEConst.ADVType.IND):
				print("connecting to [{}]".format(device.name))
				miot.connect_to_device(device)
				break

	ble = bt.BLE()
	miot = BLEMIOT(ble, scan_completed_cb)
	miot.scan()


if __name__ == "__main__":
	try:
		main()
	except KeyboardInterrupt:
		print("\nPRESS CTRL+D TO RESET DEVICE")
